import common
import queue,threading,random,requests,socket,http.client,urllib.request,urllib.parse,urllib.error,_thread,time
import unittest

globalVars = common.getGlobalVars()
lastTimeTestedInternetConnection = None


class ConnectManager(threading.Thread):
    abandonSessionQueue = None
    shutDownFlag = None
    isStartUp = None
    availiableProxies = None
    googleFrontServerIp = None
    googleFrontServerStatus = None
    
    def __init__(self):
        threading.Thread.__init__(self)
        self.isStartUp = True
        self.availiableProxies = queue.Queue()
        self.abandonSessionQueue = queue.Queue()
        self.shutDownFlag = False
        self.callback = None
        self._makeGoogleFrontServerList()
        self.GFS_SESSION_COUNT = globalVars.GFS_SESSION_COUNT
        self.GFS_ENABLE = globalVars.GOOGLE_PROXY_ENABLE
        
        self.sessionManager = SessionManager()

    def _threadInit(self):
        #find 5 just use first 5
        self._simpleTestConnection(\
            self.googleFrontServerIp[0:globalVars.GFS_SESSION_COUNT])
       
    def _makeGoogleFrontServerList(self):
        self.googleFrontServerIp = []
        for address in globalVars.IPV4_GOOGLE :
            self.googleFrontServerIp.append(GFSIP(address))
        if globalVars.IPV6_ENABLE:
            for address in globalVars.IPV6_GOOGLE :
                self.googleFrontServerIp.append(GFSIP(address))
        #randomlize
        random.shuffle(self.googleFrontServerIp)

    def checkAbandonSesson(self):
        common.logger(4,"ConnectManager.run","Daemon Thread Working")
        try:
            abandonSession = self.abandonSessionQueue.get(block = True,
                                                          timeout = 1)
            abandonSession.session.close()
            self.sessionManager.removeSession(abandonSession)
            common.logger(3,"ConnectManager.abandonSesson",\
                                  "Abandon Session")
            self._testOneProxy()
        except queue.Empty:
            pass
            
    def checkAvailiableProxies(self):
        try:
            while(True):
                result,proxy = self.availiableProxies.get(block = True,
                                                          timeout = 1)
                if result == True:
                    proxy.setUsing()
                    common.logger(3,"ConnectManager.addSession",\
                                  "Adding Session")
                    session = self._setProxySession(proxy)
                else:
                    proxy.setFinalAbandon()
                    self._testOneProxy()
                
        except queue.Empty:
            pass

    def checkLog(self):
        common.printLog()
        
    def run(self):
        if self.isStartUp :
            self.isStartUp = False
            self._threadInit()
            
        while(not self.shutDownFlag):
            self.checkAbandonSesson()
            self.checkAvailiableProxies()
            self.checkLog()

        common.logger(3,"ConnectManager.run",\
                                  "Exit loop")   
        self._closeSessions()
        self.checkLog()

    def getSessionSimple(self,block = False):
        return self.sessionManager.getSession(block)
        
        
    def getSession(self,count = 1,block = False,timeout = None,callback = None):
        #every new sessions makes a callback
        # to do make this stronger
        sessions = []
        self.callback = callback
        for _ in range(count):
            try:
                result,proxy = self.availiableProxies.get(block,timeout)
                if result == True:
                    session = self._setProxySession(proxy)
                    sessions.append(session)
                else: #get one tested
                    self._testOneProxy()
                proxy.setUsing()
            except queue.Empty:
                pass              
        return sessions

    def _testOneProxy(self):
        for proxy in self.googleFrontServerIp:
            if not proxy.isTested():
                self._simpleTestConnection([proxy,])
                break

    
    def abandonSession(self,session):
        self.abandonSessionQueue.put(session)
        

    def _setProxySession(self,proxy):
        if self.GFS_ENABLE == True:
            proxyString = proxy.getAddress()
            proxies = {"https":'https://'+proxyString,
                        'http':'http://'+proxyString}
        else:
            proxies = None
        adapter_https = requests.adapters.HTTPAdapter(pool_connections=1,
                                                     pool_maxsize=3,
                                                     max_retries=0,
                                                     pool_block=False)
        session = requests.Session()
        session.mount('https://',adapter_https)
        session.proxies = proxies
        proxy.session = session
        self.sessionManager.addSession(proxy)
        return session
    
    def _simpleTestConnection(self,proxyList):
        if not self.GFS_ENABLE:
            for _ in range(len(proxyList)):
                self.availiableProxies.put((True,GFSIP(None)))
                                    
        #first we test out our Internet connection state
        if testInternetConnection():
            for address in proxyList:
                if address.setTesting() == True:
                    common.logger(3,"ConnectManager._simpleTestConnection",\
                                      "Testing Session")
                    TCT =TestConnectionThread(address,self.availiableProxies)
                    TCT.start()
        else:
            pass #we should do something
        

    def shutDown(self):
        self.shutDownFlag = True

    def _closeSessions(self):
        for proxy in self.googleFrontServerIp:
            if proxy.session != None:
                proxy.session.close()

def testInternetConnection(force = False):
    if not force and (lastTimeTestedInternetConnection == None or\
       time.time() - lastTimeTestedInternetConnection > 1):
        return True
    else:
        try:
            f = requests.head("http://www.bing.com")
            f.close()
            return True
        except(socket.timeout)or (socket.error) or IOError:
            return False
        except Exception as e:
            common.logger(1,"ConnectionService.testInternetConnection",e)
            return False

class TestConnectionThread(threading.Thread):
    def __init__(self, address, respondQueue):
        threading.Thread.__init__(self)
        self.address = address
        self.respondQueue = respondQueue
        self.fetch_server = globalVars.FETCH_SERVER
        self.HTTP_PROTOCOL = globalVars.HTTPS_ENABLE and "https" or "http"
        
    def testConnection(self,address):
        
        try:
            f = requests.get(self.fetch_server,
                               proxies = {self.HTTP_PROTOCOL:
                                          self.HTTP_PROTOCOL+"://"+address})
            return True
        except(http.client.HTTPException):
            return True
        except(socket.timeout)or (socket.error)or urllib.error.URLError or IOError:
            return False
        except Exception as e:
            common.logger(1,"ConnectionService.testConnection",e)
            return False
        
    def run(self):
        result = self.testConnection(self.address.getAddress())
        self.respondQueue.put((result,self.address))
        
class GFSIP():
    tempAbandonTime = 300#300s = 5min
    def __init__(self,address):
        self.address = address
        self.statusLock = _thread.allocate_lock()
        self.status =   0#  not used, not tested
                    #   1   not used, testing
                    #   2   using
                    #   3   temp abandoned
                    #   4   for ever abandoned
        self.abandonedTime = 0
        self.session = None

    def _statusSetter(self, accept, newValue):
        result = False
        self.statusLock.acquire()
        if self.status in accept:
            self.status = newValue
            result = True
        self.statusLock.release()
        return result

    def _setNotUsed(self):
        return self._statusSetter([0,1,2,3,4],0)

    def setTesting(self):
        return self._statusSetter([0,],1)
    
    def setUsing(self):
        return self._statusSetter([1,],2)  
    
    def setTmpAbandon(self):
        self.abandonedTime = time.time()
        return self._statusSetter([1,2],3)

    def setFinalAbandon(self):
        return self._statusSetter([0,1,2,3],4)

    def isTested(self):
        result = True
        if self._getStatus() == 3 and\
           time.time() - self.abandonTime > self.tempAbandonTime:
            self._setNotUsed()
        self.statusLock.acquire()
        if self.status == 0:
            result = False
        self.statusLock.release()
        return result
    
    def _getStatus(self):
        self.statusLock.acquire()
        status =  self.status
        self.statusLock.release()
        return status
    
    def getAddress(self):
        return self.address
    



class SessionManager():#a threadsafe session manager
    def __init__(self):
        self.counter = 0
        self.counterLock = _thread.allocate_lock()
        self.session = []
        self.sessionEmptyQueue = queue.Queue()

    def getCounter(self):
        self.counterLock.acquire()
        counter = self.counter
        self.counterLock.release()
        return counter

    def tik(self):
        self.counterLock.acquire()
        try:
            self.counter += 1
            self.counter %= len(self.session)
        except ZeroDivisionError:
            self.counter = 0
        counter = self.counter
        self.counterLock.release()
        return counter

    def getSession(self, block = False):
        if len(self.session) == 0:
            if block == True:
                self.sessionEmptyQueue.get()
                self.sessionEmptyQueue.put(None)
            else:
                return None
        return self.session[self.tik()]

    def addSession(self, Session):# not thread safe
        self.sessionEmptyQueue.put(None)
        self.session.append(Session)

    def removeSession(self,Session):#not thread safe
        try:
            self.session.remove(Session)
            self.sessionEmptyQueue.get()#should never be blocked here
            self.tik()
        except ValueError:
            common.logger(2,"SessionManager.removeSession",\
                          "session already removed")
            pass
            
               
CM = ConnectManager()
    
def getCM():
    return CM

    #cm = getCM()
    #cm.start()
    #cm.getSession()

class ConnectionServiceUnitTest(unittest.TestCase):
    def test_GoogleIP(self):
        googleIp = GFSIP("www.test.com")
        self.assertEqual(googleIp.getAddress(),"www.test.com")
        googleIp.setTesting()
        self.assertEqual(googleIp.isTested(),True)
        googleIp.setUsing()
        self.assertEqual(googleIp._getStatus(),2)
        googleIp.setTmpAbandon()
        self.assertEqual(googleIp._getStatus(),3)
        googleIp.setFinalAbandon()
        self.assertEqual(googleIp._getStatus(),4)

    def test_ConnectionManager(self):
        cm = getCM()
        cm.start()
        session = cm.getSessionSimple(block=True)
        cm.abandonSession(session)
        cm.checkAbandonSesson()
        self.assertRaises(queue.Empty,cm.abandonSessionQueue.get,block=False)
        cm.shutDown()

    def test_SessionManager(self):
        sm = SessionManager()
        sm.addSession("1")
        sm.addSession("2")
        sm.addSession("3")
        for _ in range(10):
            sm.getSession()
        sm.removeSession("1")
        sm.removeSession("1")
        sm.removeSession("2")
        sm.removeSession("3")
        
        

if __name__ == "__main__":
    unittest.main()
    

